sk_tx_queue_clear(sk);
sk_tx_queue_clear：清除套接字的传输队列状态。
如果启用了传输队列映射（如多队列网络设备或硬件卸载），此函数会重置与队列相关的标记。
作用是确保在更新路由时，不遗留任何与旧路由关联的传输状态。

WRITE_ONCE(sk->sk_dst_pending_confirm, 0);
sk->sk_dst_pending_confirm：标志位，用于指示路由缓存是否需要确认。

如果路由缓存处于“未确认”状态，则后续需要发送数据来验证路由的可用性（例如，避免死路由）。
将其设置为 0，表明新的路由不需要挂起的确认。
WRITE_ONCE：

使用内核同步机制，确保在多核 CPU 环境下，这个标志位的修改是原子操作，防止竞态条件。

rcu_assign_pointer(sk->sk_dst_cache, dst);
rcu_assign_pointer：更新指针 sk->sk_dst_cache 的值，确保在 RCU 保护机制下操作。
将新的路由缓存（dst）赋值给套接字的路由缓存字段。
这样，后续数据包的发送会使用新的路径信息。

dst_release(old_dst);
dst_release：减少旧路由缓存的引用计数。
如果引用计数降为零，则内核会释放对应的路由缓存结构，清理相关资源。
避免内存泄漏，同时确保旧缓存不会被继续使用。


static void tcp_write_err(struct sock *sk)
{
	sk->sk_err = sk->sk_err_soft ? : ETIMEDOUT;
	sk->sk_error_report(sk);

	tcp_write_queue_purge(sk);
	tcp_done(sk);
	__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONTIMEOUT);
}

sk->sk_error_report(sk);
调用套接字的错误报告函数，通知上层应用程序当前连接出现了错误。
作用：
通知等待中的应用程序（如阻塞的系统调用）连接已经出错。
例如，select() 或 poll() 的调用会返回错误状态。

tcp_write_queue_purge(sk);
清空 TCP 的写队列，释放所有未发送或未确认的缓冲区（skb）。
作用：
防止内存泄漏。
确保连接关闭后，不再有遗留的待发送数据包。


函数 tcp_write_err 的功能
tcp_write_err 是 TCP 协议中处理超时错误或写入错误的核心函数。当 TCP 检测到传输超时或其他写入相关的致命错误时，调用此函数来清理资源并终止连接。

代码解析
1. 设置套接字错误状态
c
复制代码
sk->sk_err = sk->sk_err_soft ? : ETIMEDOUT;
sk->sk_err：套接字的错误码，表示连接的最终错误状态。

如果存在软错误（sk->sk_err_soft），优先使用它。
如果没有软错误，则设置为超时错误（ETIMEDOUT），表示连接因超时失败。
软错误：

软错误是指在尝试发送数据时遇到的非致命问题（如临时的网络问题），这些问题可能会在后续尝试中恢复。
2. 报告套接字错误
c
复制代码
sk->sk_error_report(sk);
调用套接字的错误报告函数，通知上层应用程序当前连接出现了错误。
作用：
通知等待中的应用程序（如阻塞的系统调用）连接已经出错。
例如，select() 或 poll() 的调用会返回错误状态。
3. 清理写队列
c
复制代码
tcp_write_queue_purge(sk);
清空 TCP 的写队列，释放所有未发送或未确认的缓冲区（skb）。
作用：
防止内存泄漏。
确保连接关闭后，不再有遗留的待发送数据包。
4. 终止 TCP 连接
c
复制代码
tcp_done(sk);
调用 tcp_done 完成连接的关闭和清理工作：
设置套接字状态为关闭（TCP_CLOSE）。
释放连接相关的资源。
通知协议栈该连接已不可用。



函数 tcp_write_queue_purge
tcp_write_queue_purge 是 Linux 内核中 TCP 协议栈的一个重要函数，用于清理套接字的写队列及相关资源。这个函数通常在连接被关闭、超时或发生严重错误时调用，确保释放资源并恢复套接字的状态。

代码逐步解析
1. 停止 TCP 计时器
c
复制代码
tcp_chrono_stop(sk, TCP_CHRONO_BUSY);
tcp_chrono_stop：
停止 TCP 的计时器，特别是用于跟踪连接忙碌状态的 TCP_CHRONO_BUSY。
TCP_CHRONO_BUSY：表示 TCP 正在处理数据发送或重传的阶段。
作用是停止计时并更新连接状态，防止进一步的资源消耗。
2. 清空写队列
c
复制代码
while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
    tcp_skb_tsorted_anchor_cleanup(skb);
    sk_wmem_free_skb(sk, skb);
}
__skb_dequeue：

从套接字的写队列 sk->sk_write_queue 中逐个取出数据包（skb）。
写队列中的数据包是尚未发送或者发送失败的待发送数据。
tcp_skb_tsorted_anchor_cleanup：

清理与 skb 相关的排序锚点信息。
这是 TCP 协议中用于确保数据包按序发送的一部分。
sk_wmem_free_skb：

释放写队列中每个 skb 使用的内存。
确保所有分配的内存块正确释放，防止内存泄漏。
3. 清空重传队列
c
复制代码
tcp_rtx_queue_purge(sk);
tcp_rtx_queue_purge：
清空重传队列（retransmission queue），释放因未确认而保留的数据包。
重传队列保存的是已经发送但未收到 ACK 的数据包。
4. 清理发送缓冲区缓存
c
复制代码
skb = sk->sk_tx_skb_cache;
if (skb) {
    __kfree_skb(skb);
    sk->sk_tx_skb_cache = NULL;
}
sk->sk_tx_skb_cache：

这是用于优化发送路径的缓存（存储最近发送的数据包）。
如果缓存中有数据包（skb），释放它并将缓存置为 NULL。
__kfree_skb：

释放数据包 skb 的内存。
5. 初始化发送排序队列
c
复制代码
INIT_LIST_HEAD(&tcp_sk(sk)->tsorted_sent_queue);
tsorted_sent_queue：
TCP 发送排序队列，用于记录已经发送的数据包的顺序。
调用 INIT_LIST_HEAD 初始化队列，清空已发送数据包的记录。
6. 释放内存限制资源
c
复制代码
sk_mem_reclaim(sk);
sk_mem_reclaim：
释放套接字使用的内存资源，调整内核中为该套接字保留的内存限制。
7. 清除重传提示
c
复制代码
tcp_clear_all_retrans_hints(tcp_sk(sk));
tcp_clear_all_retrans_hints：
清除与重传相关的提示信息（如快速重传和 SACK 状态）。
这些提示用于优化重传和拥塞控制逻辑，在清理时需要重置。
8. 重置发送状态和退避计数器
c
复制代码
tcp_sk(sk)->packets_out = 0;
inet_csk(sk)->icsk_backoff = 0;
tcp_sk(sk)->packets_out：

重置发送状态，表示当前没有未确认的数据包。
inet_csk(sk)->icsk_backoff：

清除退避计数器，用于计算重传超时时间（RTO）。
退避计数器会在重传超时时递增，导致 RTO 指数增长。清零可以恢复默认的超时行为。




是的，超时断开以后，你可以通过调用 tcp_v4_connect 或其他类似的连接函数重新发起连接，但需要确保以下几点：

1. 确保套接字状态已经重置
当 TCP 连接超时并调用 tcp_write_err 后，套接字的状态已经被设置为 TCP_CLOSE，且资源已经清理。如果你计划重新连接：

需要重新配置目标地址（如 sk->sk_daddr 和 sk->sk_dport）。
如果是应用层逻辑，建议直接创建一个新的套接字以避免状态遗留问题。
如果你要直接复用现有的套接字，需要手动确保状态和资源是干净的，例如调用：

c
复制代码
inet_reset_saddr(sk);  // 重置源地址
sk_dst_reset(sk);      // 清除路由缓存


inet_csk_destroy_sock 如果不调用这个，struct sock还存在，是否可以调用tcp_v4_connect再次连接
ChatGPT 说：
ChatGPT
理论上是可以的，但在实际应用中直接复用一个已经经历过错误处理的 struct sock（未通过 inet_csk_destroy_sock 释放）并尝试重新调用 tcp_v4_connect 进行连接是非常危险的，以下是原因和可能的问题：

1. inet_csk_destroy_sock 的作用
inet_csk_destroy_sock 是内核用来销毁一个套接字的函数。它负责：

释放资源：
包括路由缓存、内存分配、TCP 定时器等。
清理状态：
确保套接字的所有状态都被正确清理。
减少引用计数：
当 sk->sk_refcnt 降为 0 时，彻底释放 struct sock。
如果未调用 inet_csk_destroy_sock，虽然 struct sock 仍然存在，但它可能携带有不一致的状态信息，直接重新使用可能会导致问题。

2. 直接调用 tcp_v4_connect 的潜在问题
如果尝试直接复用 sk 调用 tcp_v4_connect，以下问题可能发生：

2.1 套接字状态不一致
在调用 tcp_write_err 或连接超时后，套接字状态可能已经被设置为 TCP_CLOSE，需要显式重置。
如果不重置状态，tcp_v4_connect 会认为当前连接已关闭，无法继续操作。
解决方法：

c
复制代码
tcp_set_state(sk, TCP_CLOSE);  // 重置状态为关闭
2.2 残留的路由缓存
旧的路由缓存可能已经失效，但仍存在于 sk->sk_dst_cache 中。
如果不清除路由缓存，tcp_v4_connect 可能会尝试使用无效的路由信息。
解决方法：

c
复制代码
sk_dst_reset(sk);  // 清理路由缓存
2.3 写队列和定时器未正确清理
调用 tcp_write_err 时，写队列可能已被清理，但未完全释放相关资源。
重连时需要确保写队列和定时器完全清空。
解决方法：

c
复制代码
tcp_write_queue_purge(sk);  // 再次清理写队列
tcp_clear_xmit_timers(sk);  // 停止所有相关的 TCP 定时器
2.4 用户状态标志的残留
套接字可能携带与之前连接相关的用户状态标志（如 sk_shutdown），这些状态可能干扰新的连接。
必须重置这些标志以确保新连接正常。
解决方法：

c
复制代码
sk->sk_shutdown = 0;  // 清理关闭标志
3. 是否可以直接复用 struct sock
在以下情况下，复用是可以的，但需要清理并重置状态：

条件：
用户进程仍然持有套接字：
如果用户进程没有关闭文件描述符，内核会保留 sk，允许继续使用。
手动重置状态和资源：
必须清理所有可能残留的状态和资源，确保套接字处于干净状态。
操作步骤：
c
复制代码
int reconnect(struct sock *sk, __be32 daddr, __be16 dport) {
    struct inet_sock *inet = inet_sk(sk);

    // 重置套接字状态
    tcp_set_state(sk, TCP_CLOSE);
    sk->sk_err = 0;            // 清理错误标志
    sk->sk_shutdown = 0;       // 清理关闭标志

    // 清理路由缓存和写队列
    sk_dst_reset(sk);
    tcp_write_queue_purge(sk);
    tcp_clear_xmit_timers(sk);

    // 设置目标地址和端口
    inet->inet_daddr = daddr;
    inet->inet_dport = dport;

    // 重新调用 tcp_v4_connect 进行连接
    return tcp_v4_connect(sk);
}
4. 实际应用中的注意事项
4.1 推荐创建新的 struct sock
虽然可以手动清理和重置 struct sock 以复用，但由于存在多个潜在的状态残留问题，推荐直接创建一个新的套接字：
c
复制代码
struct sock *new_sk = inet_create(socket_family, SOCK_STREAM, IPPROTO_TCP);
tcp_v4_connect(new_sk);
4.2 检查是否进入 TIME_WAIT 状态
如果之前的连接进入了 TIME_WAIT 状态，可能无法重新连接到同一目标。
解决方法：
启用 SO_REUSEADDR 或 SO_REUSEPORT。
c
复制代码
int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
4.3 检查引用计数
如果 sk->sk_refcnt 未正确管理，可能会导致内核资源泄漏或重复释放。
确保在调用 tcp_v4_connect 前，引用计数正确。





这两个函数分别是 ip_route_connect 和 ip_route_newports，它们都用于在 Linux 网络栈中为套接字选择路由（rtable），主要作用是通过指定的目标地址、端口等信息生成适合数据传输的路由信息。

1. 函数 ip_route_connect
功能
ip_route_connect 用于初始化并查找一条路由表项（rtable），以便为指定的目的地址和端口选择合适的网络路径。这个函数通常在 TCP 连接建立（如三次握手）时使用。

参数解析
fl4：flowi4 结构，存储传输层的路径信息（如源地址、目标地址、端口、协议等）。
dst：目标 IP 地址。
src：源 IP 地址。
tos：服务类型（Type of Service）。
oif：出接口（outgoing interface）的索引，指定从哪个网络设备发送。
protocol：协议号（如 IPPROTO_TCP）。
sport：源端口。
dport：目标端口。
sk：套接字结构。
逻辑流程
初始化路径信息

c
复制代码
ip_route_connect_init(fl4, dst, src, tos, oif, protocol, sport, dport, sk);
初始化 flowi4 结构，填充目标地址、源地址、端口、协议等信息。
检查目标和源地址

c
复制代码
if (!dst || !src) {
    rt = __ip_route_output_key(net, fl4);
    if (IS_ERR(rt))
        return rt;
    ip_rt_put(rt);
    flowi4_update_output(fl4, oif, tos, fl4->daddr, fl4->saddr);
}
如果目标地址或源地址未设置，调用 __ip_route_output_key 查找一条默认路由，并更新路径信息。
ip_rt_put(rt)：释放多余的引用计数。
调用安全检查

c
复制代码
security_sk_classify_flow(sk, flowi4_to_flowi_common(fl4));
根据安全策略对路径信息进行检查和分类。
返回最终的路由表项

c
复制代码
return ip_route_output_flow(net, fl4, sk);
调用 ip_route_output_flow 完成路径查找，并返回最终的路由表项。
总结
ip_route_connect 的核心功能是为指定的目标地址、端口和协议选择一条合适的路由，适用于初次建立连接时的路由查找。

2. 函数 ip_route_newports
功能
ip_route_newports 用于更新已有的路由表项（rtable），当需要更改源或目标端口时（例如动态端口重新绑定）调用。该函数确保端口更新后路由信息一致。

参数解析
fl4：flowi4 结构，存储路径信息。
rt：原始的路由表项。
orig_sport：原始源端口。
orig_dport：原始目标端口。
sport：新的源端口。
dport：新的目标端口。
sk：套接字结构。
逻辑流程
检查端口是否发生变化

c
复制代码
if (sport != orig_sport || dport != orig_dport) {
比较原始端口与新的端口。如果端口发生变化，则需要更新路由。
更新路径信息

c
复制代码
fl4->fl4_dport = dport;
fl4->fl4_sport = sport;
ip_rt_put(rt);
flowi4_update_output(fl4, sk->sk_bound_dev_if,
                     RT_CONN_FLAGS(sk), fl4->daddr, fl4->saddr);
更新 flowi4 中的端口信息。
释放旧的路由表项 rt。
调用 flowi4_update_output 更新路径信息。
调用安全检查

c
复制代码
security_sk_classify_flow(sk, flowi4_to_flowi_common(fl4));
根据安全策略对路径信息进行检查和分类。
返回新的路由表项

c
复制代码
return ip_route_output_flow(sock_net(sk), fl4, sk);
通过 ip_route_output_flow 查找并返回新的路由表项。
如果端口未发生变化

c
复制代码
return rt;
如果端口未变，直接返回原始路由表项，无需更新。
总结
ip_route_newports 专注于处理端口更新场景，确保端口变更后路径信息与路由表项保持一致。


两者的对比
特性	ip_route_connect	ip_route_newports
用途	初始化并查找路由	在端口更新时重新查找路由
触发条件	初次建立连接或路径查找	源或目标端口发生变化
关键操作	填充路径信息并查找合适路由	更新路径信息，特别是端口
是否释放旧路由表项	不释放（新创建的无需释放）	释放旧的路由表项（ip_rt_put(rt)）
典型场景	TCP 初次建立连接	端口动态绑定或端口迁移场景
应用场景
ip_route_connect

在 TCP 建立连接时调用。
用于设置目标 IP 地址、端口和路由。
ip_route_newports

在 NAT 环境下端口映射发生变化时调用。
在 TCP 动态端口绑定时重新查找路由。


当前链接如果因为超时断开以后，我调用sk_dst_reset之后，再重新调用tcp_v4_connect是否就会释放旧路由，重新产生新路由，并且不会造成路由泄露
ChatGPT 说：
ChatGPT
是的，以下是调用流程和原理，确保超时连接重新建立后旧路由能正确释放，新路由能正确生成，并且不会导致路由泄露。

核心逻辑
sk_dst_reset 清理旧路由缓存：

sk_dst_reset 清除当前套接字（sk）中的路由缓存指针（sk->sk_dst_cache），并调用 dst_release 减少旧路由的引用计数。
如果旧路由引用计数降为零（即没有其他引用），旧路由表项会被释放，确保没有路由泄露。
重新调用 tcp_v4_connect 生成新路由：

tcp_v4_connect 内部调用 ip_route_connect，会基于新的连接参数查找或生成新的路由表项。
生成的新路由会自动存储在 sk->sk_dst_cache 中。